﻿
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

bool Check(int* matrix_OUT, int* matrix_OUT_CUDA, int size)
{
	bool result = true;

	for (int i = 0; i < size; i++)
	{
		if (matrix_OUT[i] != matrix_OUT_CUDA[i]) result = false;
	}

	return result;
}

__global__ void matrixAddKernel(int numThreads, int* matrix1, int* matrix2, int* matrix_OUT, int size)
{
    int ID = blockIdx.x * blockDim.x + threadIdx.x;
	int partSize = size / numThreads;
	int startPoint = ID * partSize;
	int finishPoint = (ID + 1) * partSize;

	for (int i = startPoint; i < finishPoint; i++)
	{
		matrix_OUT[i] = matrix1[i] + matrix2[i];
	}
    
}

cudaError_t addWithCuda(int* matrix1, int* matrix2, int* matrix_OUT, size_t size)
{
    int *dev_matrix1 = 0;
    int *dev_matrix2 = 0;
    int *dev_matrix_OUT = 0;
    cudaError_t cudaStatus;

    // Choose which GPU to run on, change this on a multi-GPU system.
    cudaStatus = cudaSetDevice(0);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
        goto Error;
    }

    // Allocate GPU buffers for three vectors (two input, one output)    .
    cudaStatus = cudaMalloc((void**)&dev_matrix_OUT, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_matrix1, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_matrix2, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    // Copy input vectors from host memory to GPU buffers.
    cudaStatus = cudaMemcpy(dev_matrix1, matrix1, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    cudaStatus = cudaMemcpy(dev_matrix2, matrix2, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    // Launch a kernel on the GPU with one thread for each element.
	int block_size = 250;
	int block_num = 25;
	int numThreads = block_num * block_size;

    matrixAddKernel<<<block_num, block_size>>>(numThreads, dev_matrix1, dev_matrix2, dev_matrix_OUT, size);

    // cudaDeviceSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaStatus = cudaDeviceSynchronize();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
        goto Error;
    }

    // Copy output vector from GPU buffer to host memory.
    cudaStatus = cudaMemcpy(matrix_OUT, dev_matrix_OUT, size * sizeof(int), cudaMemcpyDeviceToHost);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

Error:
    cudaFree(dev_matrix_OUT);
    cudaFree(dev_matrix1);
    cudaFree(dev_matrix2);
    
    return cudaStatus;
}

void StableMatrixSum(int* matrix1, int* matrix2, int* matrix_OUT, int size)
{
	for (int i = 0; i<size ; i++)
	{
		matrix_OUT[i] = matrix1[i] + matrix2[i];
	}
}

int main()
{
	srand(time(NULL));

	int coll = 1000, rows = 1000;
	int randmax = 1000;
	int* matrix1 = new int[coll*rows];
	int* matrix2 = new int[coll*rows];
	int* matrix_OUT = new int[coll*rows];
	int* matrix_OUT_CUDA = new int[coll*rows];
	int testRandom = rand() % randmax;
	for (int i = 0; i<coll*rows ; i++)
	{
		matrix1[i] = rand() % randmax;
		matrix2[i] = rand() % randmax;
		matrix_OUT[i] = 0;
		matrix_OUT_CUDA[i] = 0;
	}

	printf("Creatng input matrix....\n\n");
	printf("CUDA calculating....\n");
	int time1 = GetTickCount();

	cudaError_t cudaStatus = addWithCuda(matrix1, matrix2, matrix_OUT_CUDA, coll*rows);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "addWithCuda failed!");
        return 1;
    }
	time1 =  GetTickCount() - time1;
	printf("Success....Time = %d\n\n",time1);


	printf("Stable calculating....\n");
	time1 = GetTickCount();
	StableMatrixSum(matrix1, matrix2, matrix_OUT, coll*rows);
	time1 =  GetTickCount() - time1;
	printf("Success....Time = %d\n\n",time1);

	printf("TESTING....");
	bool result = Check(matrix_OUT, matrix_OUT_CUDA, coll*rows);
	if (result) printf("OK\n");
	else printf("FALSE\n");
	return 0;
}